import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/song.dart';

class AlgerCategory {
  final String name;
  AlgerCategory(this.name);
}

class AlgerPlaylist {
  final String id;
  final String name;
  final String coverImgUrl;
  // 播放量（部分接口返回），用于在首页展示热度
  final int playCount;
  // 排行榜更新频率（如 每天更新、每周更新），部分接口返回
  final String? updateFrequency;
  AlgerPlaylist({required this.id, required this.name, required this.coverImgUrl, this.playCount = 0, this.updateFrequency});
}

class AlgerArtist {
  final String id;
  final String name;
  final String picUrl;
  AlgerArtist({required this.id, required this.name, required this.picUrl});
}

class AlgerArtistDetail {
  final String id;
  final String name;
  final String? briefDesc;
  final String? avatarUrl;
  final String? coverUrl;
  AlgerArtistDetail({
    required this.id,
    required this.name,
    this.briefDesc,
    this.avatarUrl,
    this.coverUrl,
  });
}

class AlgerAlbum {
  final String id;
  final String name;
  final String picUrl;
  // 发行日期（格式化后的字符串，例如 2024-03-21）
  final String? publishDate;
  AlgerAlbum({required this.id, required this.name, required this.picUrl, this.publishDate});
}

class AlgerMv {
  final String id;
  final String name;
  final String coverUrl;
  final String artistName;
  final int? durationMs;
  final int? playCount;
  AlgerMv({
    required this.id,
    required this.name,
    required this.coverUrl,
    required this.artistName,
    this.durationMs,
    this.playCount,
  });
}

class AlgerApiService {
  static const String base = 'http://mc.alger.fun/api';
  final http.Client _client;
  AlgerApiService({http.Client? client}) : _client = client ?? http.Client();

  String _withThumbnail300(String url) {
    //将url中的参数thumbnail 参数值替换为 300y300
    return url.replaceAllMapped(RegExp(r'thumbnail=([^&]+)'), (match) {
      final thumbnail = match.group(1);
      if (thumbnail != null) {
        return 'thumbnail=300y300';
      }
      return '';
    });
  }

  String _withThumbnail200(String url) {
    // 将url中的参数thumbnail 值替换为 200y200（若存在）
    return url.replaceAllMapped(RegExp(r'thumbnail=([^&]+)'), (match) {
      final thumbnail = match.group(1);
      if (thumbnail != null) {
        return 'thumbnail=200y200';
      }
      return '';
    });
  }

  // 获取排行榜列表（官方榜单）
  Future<List<AlgerPlaylist>> fetchToplists() async {
    final uri = Uri.parse('$base/toplist?device=mobile');
    print('fetchToplists $uri');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return [];
    final body = jsonDecode(resp.body);
    dynamic data = (body is Map)
        ? (body['data'] ?? body['list'] ?? body['toplist'] ?? body)
        : body;
    final List<dynamic> list = (data is List)
        ? data
        : (data is Map && data['list'] is List)
            ? (data['list'] as List)
            : (data is Map && data['data'] is List)
                ? (data['data'] as List)
                : [];
    return list
        .map((e) {
          final id = _pickId(e);
          final name = (e is Map) ? (e['name'] ?? e['title'] ?? '') : '';
          final cover = (e is Map)
              ? (e['coverImgUrl'] ?? e['picUrl'] ?? e['img'] ?? e['cover'] ?? '')
              : '';
          final pc = (e is Map)
              ? (((e['playCount'] ?? e['playcount'] ?? e['playnum'] ?? 0) as num?)?.toInt() ?? 0)
              : 0;
          final uf = (e is Map)
              ? (e['updateFrequency'] ?? e['updateDesc'] ?? e['updateTimeDesc'] ?? e['updateTime'] ?? '')
              : '';
          return AlgerPlaylist(
            id: id,
            name: name.toString(),
            coverImgUrl: _withThumbnail300(cover.toString()),
            playCount: pc,
            updateFrequency: (uf is String && uf.isNotEmpty) ? uf.toString() : null,
          );
        })
        .where((p) => p.id.isNotEmpty && p.name.isNotEmpty)
        .toList();
  }

  //在url后追加参数param=100y100
  String _withParam100(String ? url) {
    // 在url后追加参数param=100y100
    return url != null ? '${url.trim()}?param=100y100' : '';
  }

  //在url后追加参数param=300y300
  String _withParam300(String ? url) {
    // 在url后追加参数param=300y300
    return url != null ? '${url.trim()}?param=300y300' : '';
  }

  String _withParam200(String ? url) {
    // 在url后追加参数param=200y200
    return url != null ? '${url.trim()}?param=200y200' : '';
  }

  // 获取分类
  Future<List<AlgerCategory>> fetchCategories() async {
    final uri = Uri.parse('$base/playlist/catlist?device=mobile');
    print('fetchCategories $uri');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return [];
    final body = jsonDecode(resp.body);
    final dynamic data = (body is Map) ? (body['data'] ?? body['sub'] ?? body) : body;
    final List<dynamic> list = data is Map && data['sub'] is List
        ? (data['sub'] as List)
        : (data is List ? data : []);
    return list
        .map((e) {
          final name = (e is Map) ? (e['name'] ?? e['title'] ?? e['cat'] ?? '') : e?.toString() ?? '';
          return name.toString();
        })
        .where((s) => s.isNotEmpty)
        .map((s) => AlgerCategory(s))
        .toList();
  }

  // 获取分类下的歌单
  Future<List<AlgerPlaylist>> fetchTopPlaylists({required String cat, int limit = 22, int offset = 0}) async {
    final uri = Uri.parse('$base/top/playlist?cat=$cat&limit=$limit&offset=$offset&device=mobile');
    print('fetchTopPlaylists $uri');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return [];
    final body = jsonDecode(resp.body);
    dynamic data = (body is Map) ? (body['data'] ?? body['playlists'] ?? body['result'] ?? body['list'] ?? body) : body;
    final List<dynamic> list = (data is List)
        ? data
        : (data is Map && data['playlists'] is List)
            ? (data['playlists'] as List)
            : (data is Map && data['list'] is List)
                ? (data['list'] as List)
                : [];
    return list.map((e) {
      final id = _pickId(e);
      final name = (e is Map) ? (e['name'] ?? e['title'] ?? '') : '';
      final cover = (e is Map)
          ? (e['coverImgUrl'] ?? e['picUrl'] ?? e['img'] ?? e['cover'] ?? '')
          : '';
      final pc = (e is Map)
          ? (((e['playCount'] ?? e['playcount'] ?? e['playnum'] ?? 0) as num?)?.toInt() ?? 0)
          : 0;
      return AlgerPlaylist(
        id: id,
        name: name.toString(),
        coverImgUrl: _withThumbnail300(cover.toString()),
        playCount: pc,
      );
    }).where((p) => p.id.isNotEmpty && p.name.isNotEmpty).toList();
  }

  // 获取歌单详情
  Future<List<Song>> fetchPlaylistTracks(String playlistId) async {
    final uri = Uri.parse('$base/playlist/detail?id=$playlistId&device=mobile');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return [];
    final body = jsonDecode(resp.body);
    dynamic data = (body is Map) ? (body['data'] ?? body['playlist'] ?? body) : body;
    final List<dynamic> tracks = (data is Map && data['tracks'] is List)
        ? (data['tracks'] as List)
        : (data is Map && data['data'] is Map && (data['data'] as Map)['tracks'] is List)
            ? (((data['data'] as Map)['tracks'] as List))
            : [];
    return tracks.map((t) {
      final id = _pickId(t);
      final name = (t is Map) ? (t['name'] ?? '') : '';
      final artists = _pickArtists(t);
      final albumName = _pickAlbumName(t);
      final albumPic = _pickAlbumPic(t);
      // 替换参数为 100y100
      final albumPic100 = _withParam100(albumPic);
      return Song(
        id: id,
        name: name.toString(),
        artist: artists,
        album: albumName,
        picId: '',
        urlId: '',
        lyricId: '',
        source: 'alger',
        albumImageUrl: albumPic100,
      );
    }).where((s) => (s.id ?? '').isNotEmpty && s.name.isNotEmpty).toList();
  }

  Future<String?> fetchPlaylistDescription(String playlistId) async {
    final uri = Uri.parse('$base/playlist/detail?id=$playlistId&device=mobile');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return null;
    final body = jsonDecode(resp.body);
    dynamic data = (body is Map) ? (body['data'] ?? body['playlist'] ?? body) : body;
    if (data is Map) {
      if (data['description'] is String) return data['description'] as String;
      if (data['data'] is Map && ((data['data'] as Map)['description'] is String)) {
        return ((data['data'] as Map)['description'] as String);
      }
    }
    return null;
  }

  // 获取专辑歌曲列表
  Future<List<Song>> fetchAlbumTracks(String albumId) async {
    final uri = Uri.parse('$base/album?id=$albumId&device=mobile');
    print('fetchAlbumTracks $uri');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return [];
    final body = jsonDecode(resp.body);
    dynamic data = (body is Map) ? (body['data'] ?? body) : body;
    // 兼容不同结构：直接 songs，或 data.songs
    final List<dynamic> tracks = (data is Map && data['songs'] is List)
        ? (data['songs'] as List)
        : (data is Map && data['data'] is Map && (data['data'] as Map)['songs'] is List)
            ? (((data['data'] as Map)['songs'] as List))
            : [];
    return tracks.map((t) {
      final id = _pickId(t);
      final name = (t is Map) ? (t['name'] ?? '') : '';
      final artists = _pickArtists(t);
      final albumName = _pickAlbumName(t);
      final albumPic = _pickAlbumPic(t);
      final albumPic100 = _withParam100(albumPic);
      return Song(
        id: id,
        name: name.toString(),
        artist: artists,
        album: albumName,
        picId: '',
        urlId: '',
        lyricId: '',
        source: 'alger',
        albumImageUrl: albumPic100,
      );
    }).where((s) => (s.id ?? '').isNotEmpty && s.name.isNotEmpty).toList();
  }

  // 获取专辑简介
  Future<String?> fetchAlbumDescription(String albumId) async {
    final uri = Uri.parse('$base/album?id=$albumId&device=mobile');
    print('fetchAlbumDescription $uri');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return null;
    final body = jsonDecode(resp.body);
    dynamic data = (body is Map) ? (body['data'] ?? body) : body;
    // 兼容 album.description 或 data.album.description
    if (data is Map) {
      if (data['album'] is Map && ((data['album'] as Map)['description'] is String)) {
        return ((data['album'] as Map)['description'] as String);
      }
      if (data['data'] is Map && ((data['data'] as Map)['album'] is Map)) {
        final album = (data['data'] as Map)['album'] as Map;
        if (album['description'] is String) return album['description'] as String;
      }
    }
    return null;
  }

  // 获取歌曲url
  Future<String?> fetchSongUrl(String id, {String level = 'higher'}) async {
    final ts = DateTime.now().millisecondsSinceEpoch;
    final uri = Uri.parse('$base/song/url/v1?id=$id&level=$level&timestamp=$ts&device=mobile');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return null;
    final body = jsonDecode(resp.body);
    dynamic data = (body is Map) ? (body['data'] ?? body) : body;
    if (data is List && data.isNotEmpty) {
      final first = data.first;
      if (first is Map && first['url'] is String) return first['url'] as String;
    }
    if (data is Map && data['data'] is List && (data['data'] as List).isNotEmpty) {
      final first = (data['data'] as List).first;
      if (first is Map && first['url'] is String) return first['url'] as String;
    }
    return null;
  }

  // 获取歌词
  Future<(String? lyric, String? tlyric)> fetchLyric(String id) async {
    final ts = DateTime.now().millisecondsSinceEpoch;
    final uri = Uri.parse('$base/lyric?id=$id&timestamp=$ts&device=mobile');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return (null, null);
    final body = jsonDecode(resp.body);
    final map = body is Map ? body : {};
    String? lrc;
    String? tlrc;
    // Netease style
    if (map['lrc'] is Map) {
      final lrcMap = map['lrc'] as Map;
      if (lrcMap['lyric'] is String) lrc = lrcMap['lyric'] as String;
    }
    if (map['tlyric'] is Map) {
      final tlrcMap = map['tlyric'] as Map;
      if (tlrcMap['lyric'] is String) tlrc = tlrcMap['lyric'] as String;
    }
    // Generic data holder
    if (lrc == null && map['data'] is Map && (map['data'] as Map)['lyric'] is String) {
      lrc = (map['data'] as Map)['lyric'] as String;
    }
    return (lrc, tlrc);
  }

  // 获取热门艺人列表
  Future<List<AlgerArtist>> fetchTopArtists({int limit = 30, int offset = 0}) async {
    final uri = Uri.parse('$base/top/artists?offset=$offset&limit=$limit&device=mobile');
    print('fetchTopArtists $uri');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return [];
    final body = jsonDecode(resp.body);
    final dynamic data = (body is Map) ? (body['data'] ?? body['artists'] ?? body) : body;
    final List list = (data is List)
        ? data
        : (data is Map && data['artists'] is List)
            ? data['artists'] as List
            : [];
    return list.map((e) {
      final id = _pickId(e);
      final name = (e is Map) ? (e['name'] ?? '') : '';
      final pic = (e is Map)
          ? (e['picUrl'] ?? e['avatar'] ?? e['img1v1Url'] ?? e['cover'] ?? '')
          : '';
      return AlgerArtist(
        id: id,
        name: name.toString(),
        picUrl: _withParam200(pic.toString()),
      );
    }).where((a) => a.id.isNotEmpty && a.name.isNotEmpty).toList();
  }

  // 获取艺人详情（含简介/头像/封面）
  Future<AlgerArtistDetail?> fetchArtistDetail(String artistId) async {
    final uri = Uri.parse('$base/artist/detail?id=$artistId&device=mobile');
    print('fetchArtistDetail $uri');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return null;
    final body = jsonDecode(resp.body);
    dynamic data = (body is Map) ? (body['data'] ?? body['artist'] ?? body) : body;
    if (data is Map) {
      final id = (data['id'] ?? data['artistId'] ?? artistId).toString();
      final name = (data['name'] ?? (data['artist'] is Map ? ((data['artist'] as Map)['name'] ?? '') : '')).toString();
      final brief = (data['briefDesc'] ?? (data['artist'] is Map ? ((data['artist'] as Map)['briefDesc'] ?? '') : null))?.toString();
      final avatar = (data['avatar'] ?? (data['artist'] is Map ? ((data['artist'] as Map)['avatar'] ?? '') : null))?.toString();
      final cover = (data['cover'] ?? (data['artist'] is Map ? ((data['artist'] as Map)['cover'] ?? '') : null))?.toString();
      return AlgerArtistDetail(
        id: id,
        name: name,
        briefDesc: brief?.isNotEmpty == true ? brief : null,
        avatarUrl: avatar?.isNotEmpty == true ? _withThumbnail200(avatar!) : null,
        coverUrl: cover?.isNotEmpty == true ? _withThumbnail200(cover!) : null,
      );
    }
    return null;
  }

  // 获取艺人热门歌曲（先取ID列表再获取详情）
  Future<List<Song>> fetchArtistHotSongs(String artistId, {int limit = 50, int offset = 0}) async {
    final idsUri = Uri.parse('$base/artist/songs?id=$artistId&limit=$limit&offset=$offset&order=hot&device=mobile');
    print('fetchArtistHotSongs $idsUri');
    final idsResp = await _client.get(idsUri);
    if (idsResp.statusCode != 200) return [];
    final idsBody = jsonDecode(idsResp.body);
    final dynamic idsData = (idsBody is Map) ? (idsBody['data'] ?? idsBody['songs'] ?? idsBody) : idsBody;
    final List idList = (idsData is List)
        ? idsData
        : (idsData is Map && idsData['songs'] is List)
            ? (idsData['songs'] as List)
            : [];
    final ids = idList
        .map((e) => e is Map ? (e['id']?.toString() ?? _pickId(e)) : e?.toString() ?? '')
        .where((s) => s.isNotEmpty)
        .toList();
    if (ids.isEmpty) return [];
    return fetchSongsDetailsByIds(ids);
  }

  // 根据歌曲ID列表获取详情
  Future<List<Song>> fetchSongsDetailsByIds(List<String> ids) async {
    final idsParam = ids.join(',');
    final uri = Uri.parse('$base/song/detail?ids=$idsParam&device=mobile');
    print('fetchSongsDetailsByIds $uri');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return [];
    final body = jsonDecode(resp.body);
    dynamic data = (body is Map) ? (body['data'] ?? body['songs'] ?? body) : body;
    final List<dynamic> list = (data is List)
        ? data
        : (data is Map && data['songs'] is List)
            ? (data['songs'] as List)
            : [];
    return list.map((t) {
      final id = _pickId(t);
      final name = (t is Map) ? (t['name'] ?? '') : '';
      final artists = _pickArtists(t);
      final albumName = _pickAlbumName(t);
      final albumPic = _pickAlbumPic(t);
      final albumPic100 = _withParam100(albumPic);
      return Song(
        id: id,
        name: name.toString(),
        artist: artists,
        album: albumName,
        picId: '',
        urlId: '',
        lyricId: '',
        source: 'alger',
        albumImageUrl: albumPic100,
      );
    }).where((s) => (s.id ?? '').isNotEmpty && s.name.isNotEmpty).toList();
  }

  // 获取艺人专辑列表
  Future<List<AlgerAlbum>> fetchArtistAlbums(String artistId, {int limit = 50, int offset = 0}) async {
    final uri = Uri.parse('$base/artist/album?id=$artistId&limit=$limit&offset=$offset&device=mobile');
    print('fetchArtistAlbums $uri');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return [];
    final body = jsonDecode(resp.body);
    dynamic data = (body is Map) ? (body['data'] ?? body['hotAlbums'] ?? body['albums'] ?? body) : body;
    final List list = (data is List)
        ? data
        : (data is Map && data['hotAlbums'] is List)
            ? data['hotAlbums'] as List
            : (data is Map && data['albums'] is List)
                ? data['albums'] as List
                : [];
    return list.map((e) {
      final id = _pickId(e);
      final name = (e is Map) ? (e['name'] ?? '') : '';
      final pic = (e is Map)
          ? (e['picUrl'] ?? e['cover'] ?? (e['al'] is Map ? ((e['al'] as Map)['picUrl'] ?? '') : ''))
          : '';
      final publishDate = _pickPublishDate(e);
      return AlgerAlbum(
        id: id,
        name: name.toString(),
        picUrl: _withThumbnail200(pic.toString()),
        publishDate: publishDate,
      );
    }).where((a) => a.id.isNotEmpty && a.name.isNotEmpty).toList();
  }

  // Helpers
  String _pickId(dynamic e) {
    if (e is Map) {
      final id = e['id'] ?? e['pid'] ?? e['rid'] ?? e['songId'] ?? e['mid'] ?? '';
      return id.toString();
    }
    return '';
  }

  List<String> _pickArtists(dynamic t) {
    if (t is Map) {
      if (t['ar'] is List) {
        return (t['ar'] as List)
            .map((a) => (a is Map) ? (a['name']?.toString() ?? '') : a?.toString() ?? '')
            .where((s) => s.isNotEmpty)
            .toList();
      }
      if (t['artist'] is List) {
        return (t['artist'] as List)
            .map((s) => s?.toString() ?? '')
            .where((s) => s.isNotEmpty)
            .toList();
      }
      if (t['artists'] is List) {
        return (t['artists'] as List)
            .map((s) => s?.toString() ?? '')
            .where((s) => s.isNotEmpty)
            .toList();
      }
    }
    return [];
  }

  String _pickAlbumName(dynamic t) {
    if (t is Map) {
      if (t['al'] is Map && (t['al'] as Map)['name'] is String) {
        return ((t['al'] as Map)['name'] as String);
      }
      if (t['album'] is String) return t['album'] as String;
      if (t['al'] is String) return t['al'] as String;
    }
    return '';
  }

  String? _pickAlbumPic(dynamic t) {
    if (t is Map) {
      if (t['al'] is Map && (t['al'] as Map)['picUrl'] is String) {
        return ((t['al'] as Map)['picUrl'] as String);
      }
      if (t['album'] is Map && (t['album'] as Map)['picUrl'] is String) {
        return ((t['album'] as Map)['picUrl'] as String);
      }
      if (t['picUrl'] is String) return t['picUrl'] as String;
      if (t['coverImgUrl'] is String) return t['coverImgUrl'] as String;
      if (t['img'] is String) return t['img'] as String;
    }
    return null;
  }

  // 提取并格式化发行日期
  String? _pickPublishDate(dynamic e) {
    try {
      if (e is Map) {
        // 常见字段：publishTime（毫秒）、publishDate（字符串）、publicTime、pubTime
        dynamic v = e['publishTime'] ?? e['publishDate'] ?? e['publicTime'] ?? e['pubTime'];
        if (v == null && e['al'] is Map) {
          final al = e['al'] as Map;
          v = al['publishTime'] ?? al['publishDate'] ?? al['publicTime'] ?? al['pubTime'];
        }
        if (v == null) return null;
        if (v is String) {
          // 如果是纯数字字符串，当做时间戳；否则直接返回（如已是 yyyy-MM-dd）
          final numRe = RegExp(r'^\d{10,13}$');
          if (!numRe.hasMatch(v)) return v; // 已是可读日期
          final ms = int.tryParse(v);
          if (ms == null) return null;
          return _formatDateFromMs(ms);
        }
        if (v is int) {
          return _formatDateFromMs(v);
        }
      }
    } catch (_) {}
    return null;
  }

  String _formatDateFromMs(int ts) {
    // 如果是秒级，转换为毫秒
    if (ts < 100000000000) {
      ts *= 1000;
    }
    final dt = DateTime.fromMillisecondsSinceEpoch(ts);
    String two(int n) => n.toString().padLeft(2, '0');
    return '${dt.year}-${two(dt.month)}-${two(dt.day)}';
  }

  // 获取 MV 列表
  Future<List<AlgerMv>> fetchMvs({int limit = 42, int offset = 0, String area = ''}) async {
    final ts = DateTime.now().millisecondsSinceEpoch;
    final uri = Uri.parse('$base/mv/all?limit=$limit&offset=$offset&area=$area&timestamp=$ts&device=mobile');
    print('fetchMvs $uri');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return [];
    final body = jsonDecode(resp.body);
    dynamic data = (body is Map) ? (body['data'] ?? body['mvs'] ?? body['result'] ?? body) : body;
    final List list = (data is List)
        ? data
        : (data is Map && data['mvs'] is List)
            ? data['mvs'] as List
            : [];
    return list.map((e) {
      final id = _pickId(e);
      final name = (e is Map) ? (e['name'] ?? e['title'] ?? '') : '';
      final cover = (e is Map)
          ? (e['cover'] ?? e['coverImgUrl'] ?? e['picUrl'] ?? e['img'] ?? '')
          : '';
      final artistName = (e is Map) ? (e['artistName'] ?? e['artist'] ?? '') : '';
      final duration = (e is Map) ? (e['duration'] ?? e['durationms'] ?? e['durationMs'] ?? null) : null;
      int? durationMs;
      if (duration is int) {
        // 有的接口返回秒，有的返回毫秒
        durationMs = duration < 100000 ? duration * 1000 : duration;
      } else if (duration is String) {
        final d = int.tryParse(duration);
        if (d != null) durationMs = d < 100000 ? d * 1000 : d;
      }
      // 播放量
      dynamic pc = (e is Map) ? (e['playCount'] ?? e['playcount'] ?? null) : null;
      int? playCount;
      if (pc is int) {
        playCount = pc;
      } else if (pc is String) {
        playCount = int.tryParse(pc);
      }
      return AlgerMv(
        id: id,
        name: name.toString(),
        coverUrl: _withParam300(cover?.toString()),
        artistName: artistName.toString(),
        durationMs: durationMs,
        playCount: playCount,
      );
    }).where((m) => m.id.isNotEmpty && m.name.isNotEmpty).toList();
  }

  // 获取 MV 播放 URL
  Future<String?> fetchMvUrl(String id) async {
    final ts = DateTime.now().millisecondsSinceEpoch;
    final uri = Uri.parse('$base/mv/url?id=$id&timestamp=$ts&device=mobile');
    print('fetchMvUrl $uri');
    final resp = await _client.get(uri);
    if (resp.statusCode != 200) return null;
    final body = jsonDecode(resp.body);
    dynamic data = (body is Map) ? (body['data'] ?? body['url'] ?? body) : body;
    if (data is String) return data;
    if (data is Map && data['url'] is String) return data['url'] as String;
    return null;
  }
}
